package com.foreveross.bsl.push.application.impl.channel.instant;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.PostConstruct;
import javax.inject.Inject;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import com.foreveross.bsl.common.utils.mapper.JsonMapper;
import com.foreveross.bsl.push.application.impl.DsRouterHelper;
import com.foreveross.bsl.push.application.impl.DsRouterPushId;
import com.foreveross.bsl.push.application.impl.channel.BaseChannel;
import com.foreveross.bsl.push.application.impl.channel.SendFeedbackable;
import com.foreveross.bsl.push.application.impl.channel.SendTarget;
import com.foreveross.bsl.push.application.impl.channel.SendTarget.TargetIdentity;

@Component
public class InstantChannel extends BaseChannel {

	private final Logger log = LoggerFactory.getLogger(this.getClass());

	public final static String KEY_HOST = "instant.host";
	public final static String KEY_PORT = "instant.port";
	public final static String KEY_PATH = "instant.path";
	public final static String KEY_APPKEY = "instant.appKey";
	public final static String KEY_SECRET = "instant.secret";

	private String serverHost;
	private int serverPort;
	private String serverPath;
	private String appKey;
	private String secret;

	private final JsonMapper jsonMapper;

	@Inject
	private SendFeedbackable feedbacker;

	@Override
	public String getChannelId() {
		return "instant";
	}

	public InstantChannel() {
		jsonMapper = JsonMapper.nonEmptyMapper();
	}

	public int getServerPort() {
		return serverPort;
	}

	public void setServerPort(int serverPort) {
		this.serverPort = serverPort;
	}

	public String getServerHost() {
		return serverHost;
	}

	public void setServerHost(String serverHost) {
		this.serverHost = serverHost;
	}
	
	public String getServerPath() {
		return serverPath;
	}

	public void setServerPath(String serverPath) {
		this.serverPath = serverPath;
	}

	public String getAppKey() {
		return appKey;
	}

	public void setAppKey(String appKey) {
		this.appKey = appKey;
	}

	public String getSecret() {
		return secret;
	}

	public void setSecret(String secret) {
		this.secret = secret;
	}

	@PostConstruct
	public void init() {
		// 设置node-push主机
		this.setServerHost(this.getChannelConfig().getProperty(KEY_HOST));
		this.setServerPath(this.getChannelConfig().getProperty(KEY_PATH));
		this.setAppKey(this.getChannelConfig().getProperty(KEY_APPKEY));
		this.setSecret(this.getChannelConfig().getProperty(KEY_SECRET));
		int port = 8869;
		try {
			port = Integer.parseInt(getChannelConfig().getProperty(KEY_PORT, "8869"));
		} catch (NumberFormatException e) {
			log.info("port={} is parse error...", getChannelConfig().getProperty(KEY_PORT));
		}
		// 设置node-push端口
		this.setServerPort(port);
		if(this.serverHost == null) {
			log.warn("instant（node-push主机地址未设置！");
		} else {
			log.info("instant（node-push）主机地址:{}，端口:{}，查询路径：{} ", this.serverHost, this.serverPort, this.serverPath);
			log.info("http://" + this.serverHost + ":" + this.serverPort +  this.serverPath);
		}
	}

	private List<DsRouterPushId> buildDsRouterPushIds(SendTarget target) {
		List<DsRouterPushId> pushIds = new ArrayList<DsRouterPushId>(target.getTargets().size());
		String dsRouter = DsRouterHelper.getCurrentDsRouter();
		for(SendTarget.TargetIdentity ti : target.getTargets()){
			pushIds.add(new DsRouterPushId(ti.getPushId(), dsRouter));
		}
		return pushIds;
	}

	private DsRouterPushId getDsRouterPushByPushId(String pushId, DsRouterPushId[] pushIdArray){
		for(DsRouterPushId drp : pushIdArray){
			if(drp.getPushId().equals(pushId)){
				return drp;
			}
		}
		throw new IllegalArgumentException("在"+pushIdArray.length+"个pushIdArray中找不到pushId："+pushId);
	}

	private String convertMessage(com.foreveross.bsl.push.domain.Message msg){
		// 处理指定需要推过去的extras中的属性
		com.foreveross.bsl.push.domain.Message t = new com.foreveross.bsl.push.domain.Message();
		t.setId(msg.getId());
		t.setTitle(msg.getTitle());
		t.setMessageType(msg.getMessageType());
		String[] deps = msg.getDirectExtrasPropertys();
		if (deps != null && deps.length > 0) {
			for (String key : deps) {
				Object value = msg.getExtra(key);
				if (value != null) {
					t.putExtra(key, value);
				}
			}
		}
		return jsonMapper.toJson(t);
	}

	/**
	 * [doSend description] 调用Node-push API推送信息（POST请求）
	 * @param target [description]
	 * @param msg    [description]
	 */
	@Override
	protected void doSend(SendTarget target, com.foreveross.bsl.push.domain.Message msg) {
		DsRouterPushId[] pushIdArray = this.buildDsRouterPushIds(target).toArray(new DsRouterPushId[0]);
		for(TargetIdentity ti : target.getTargets()) {
			DsRouterPushId drp = this.getDsRouterPushByPushId(ti.getPushId(), pushIdArray);
			try {
				if(log.isTraceEnabled()) {
					log.trace("appId:{}, deviceId:{}, token: {}", target.getAppId(), ti.getDeviceId(), ti.getToken());
				}
				// node api
				String msgJson = this.convertMessage(msg); // 抽离标题，开启推id拉详细模式
				this.feedbacker.sentSuccessfully(new Date(), drp); // 推送状态更新
				/**
				 * 构造POST请求体，开始发送请求, node-push，验证应用等
				 */
				Map<String,String> paramMap = new HashMap<String, String>();
				paramMap.put("msg", msgJson);
				paramMap.put("deviceId", ti.getDeviceId());
				paramMap.put("appId", this.getAppKey());
				paramMap.put("secret", this.getSecret());
				// zilla后台配置,得到3rd信息，开始转发...
				String result = this.getStringFromHttpPost("http://" + this.serverHost + ":" + this.serverPort +  this.serverPath, "UTF8", paramMap);
				// {code: 200, status: 'sucess', message: '推送成功!'}
				// {code: 304, status: 'failure', message: '推送失败! 原因：此设备未保持长连接。'}
				if(result.contains("200")) {
					log.info("发送消息成功。");
				} else if(result.contains("304")){
					log.info("发送离线消息成功。");
				}
				if(log.isInfoEnabled()) {
					log.info("推送到终端的消息内容({}bytes): {}", msgJson.getBytes().length, msgJson);
				}
			} catch (Exception e) {
				log.error("instant服务器异常", e);
				feedbacker.unrecoverableFailure(e.getMessage(), e, drp);
			}
		}
	}

	@Override
	protected void doClose() throws Exception {
		log.debug("{}渠道关闭", this.getChannelId());
	}

	/**
     * 通过POST请求获取网络数据
	 *
	 * @param toUrl
	 *            访问地址
	 * @param charset
	 *            数据编码格式
	 * @param params
	 * 			     参数
	 * @return 字符串内容
	 */
	private String getStringFromHttpPost(String toUrl, String charset, Map<String,String> params) throws Exception{
		InputStream is = null;
		OutputStream output = null;
		String result = null;
		try {
			log.info("访问url = =" + toUrl);
			URL url = new URL(toUrl);
			HttpURLConnection http = (HttpURLConnection) url.openConnection();
			http = (HttpURLConnection) url.openConnection();
			http.setDoOutput(true);
			http.setDoInput(true);
			http.setConnectTimeout(8000);
			http.setRequestMethod("POST");
			output = http.getOutputStream();
			StringBuilder outStr = new StringBuilder();
			Set<String> keys = params.keySet();
			for (String key : keys) {
				outStr.append("&");
				outStr.append(key);
				outStr.append("=");
				outStr.append(params.get(key));
			}
			output.write(outStr.toString().getBytes(charset == null ? "UTF8": charset));
			output.flush();
			// post返回值
			is = http.getInputStream();
			BufferedReader reader = new BufferedReader(new InputStreamReader(is, charset == null ? "UTF8": charset));
			String line = null;
			StringBuffer buffer = new StringBuffer();
			while((line = reader.readLine()) != null) {
			    buffer.append(line);
			}
			result = buffer.toString();
		} finally {
			if (is != null) {
				is.close();
			}
			if(output != null){
				output.close();
			}
		}
		return result;
	}
}